﻿
/********************************************************************
	created:	2012/12/30
	created:	30:12:2012   11:41
	filename: 	C:\GoodFrame\common\MsgBlockPool.h
	file path:	C:\GoodFrame\common
	file base:	MsgBlockPool
	file ext:	h
	author:		hgj
	
	purpose:	消息池，有线程级别的安全
	            注意：在应用时不要执行 delete 本实例，危险。因为它用到的 m_lock 若被 delete 后，但使用 CLightweightPool 实例的线程却不知道。
*********************************************************************/

#ifndef MEM_BLOCK_POOL2_H
#define MEM_BLOCK_POOL2_H

struct CORE_MSG_HEAD;

#include "MemBlock2.h"
#include <climits>
#include<cstdio>

#define  SERVER_SEND_MAX_DATA 0  // 1 表示服务端发送大数据到客户端测试，0 表示小数据

struct OUT_POOL_ELEMENT	// 未归池元素
{
	OUT_POOL_ELEMENT(){	memset(this, 0, sizeof(*this)); }

	char	szTextMark[CMemBlock2::TEXT_MARK_LEN];	// 文字标识，目的是在查询未释放的内存是知道是程序的哪一句申请的。通常是调用的函数名。

	std::chrono::high_resolution_clock::time_point timeOutPool;	// 出池时间
	std::chrono::high_resolution_clock::time_point timeDisplay;	// 列印的时间
};

// 消息池，有线程级别的安全。
// 注意：在应用时不要执行 delete 本实例，危险。因为它用到的 m_lock 若被 delete 啦，但使用 CMemBlockPool2 的线程却不知道。
class  CMemBlockPool2
{
public:
	enum 
	{ 
		DEF_MEMBLOCK_SIZE = 2048,		// CMemBlock2 的默认长度

		#if SERVER_SEND_MAX_DATA		// 服务器大数据来袭测试
				DEF_INIT_COUNT		= 1024*8,
				DEF_MAX_COUNT	= 1024*32,	// 
		#else
				DEF_INIT_COUNT = 256,
				DEF_MAX_COUNT = 1024 * 8,	// 标准值
		#endif
	};

	enum { AUTO_SHRINK_SERVICEPOOL			= 300, };  // 5 分钟

	enum TYPE_IN_HEAD	// 消息头内的类型
	{
		TIH_TOTAL_SIZE, 
		TIH_PEER_IP,
		TIH_MAIN_CODE,
		TIH_SUB_CODE,
	};


public:

	/* --------------------------------------------------------------------------
	函数说明：消息池的构造函数
	传入参数：
		pszPoolName		池的名称
		iMsgBlockSize	消息块的尺寸
		nInitElementCount	池内元素的初始数量
		nMaxElementCount	池内元素的最大数量
	-------------------------------------------------------------------------- */
	CMemBlockPool2(int iMsgBlockSize=2048, int nInitElementCount=DEF_INIT_COUNT, int nMaxElementCount=DEF_MAX_COUNT);

	~CMemBlockPool2();	// 在应用时不要执行 delete 本实例 本实例，危险。因为它用到的 m_lock 若被 delete 啦，但使用 CMemBlockPool2 的线程却不知道。

	int Test(int** pp);

	// 获得是否允许收缩
	bool get_shrink_pool();

	// 设置是否允许收缩
	void set_shrink_pool(bool bShrinkPool);

	// 设置池内初始元素的数量
	void set_init_block_count(int nInitBlkCount);

	// 设置池内元素的上限值
	void set_max_block_count(int nMaxBlkCount);

	// 设置扩展失败次数
	void set_expand_fail_times(int iExpandFailTimes);

	// 设置扩展失败时的睡眠时间，以 ms 为单位
	void set_expand_fail_sleep_time(int iExpandFailSleepTime);

	// 使用定时器自动收缩池，定时器由别的类提供，很重要
	int use_timer_shrink_pool(int nAtLessShrinkCount = 512, float fShrinkParam = 0.5);

	// 获得池里内存块的尺寸
	int get_mem_block_size();

	// 获得池内初始元素数量
	int get_init_element_count();

	bool	init();		// 初始化
	bool	uninit();	// 释放资源

	/* --------------------------------------------------------------------------
	函数说明：出池
	传入参数：
		pszTextMark 文字标识，目的是在查询未释放的内存是知道是程序的哪一句申请的。通常是调用的函数名。
		bIsWait		如果从池中取消息记录未能成功，等待 1000 毫秒
	传出参数：
		ppMsgBlock		出池的 CMemBlock2 实例
	返回值：
		成功则返回 true，否则返回 false 。
	--------------------------------------------------------------------------*/
	bool retrieve(CMemBlock2** ppMsgBlock, char* pszTextMark, bool bIsWait=true);

	/* --------------------------------------------------------------------------
	函数说明：重新装填消息头，消息块内的包体内容保持原状不变
	传入参数：
		lChannelNo	通道号
		iMainCode	主消息码
		iSubCode	辅助消息码
	传出参数：
		pMemBlock	重新装填好的消息块，消息块内的包体内容保持原状不变
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	包体内容超出消息块的长度啦
			20	消息块出池失败
			40	包头或包体赋值失败
	--------------------------------------------------------------------------*/
	int refill_head(CMemBlock2*& pMemBlock, long long lChannelNo, int iMainCode, int iSubCode);

	/* --------------------------------------------------------------------------
	函数说明：重新装填消息块，通道号、主码、辅助码保持原来的值不变
	传入参数：
	    pMemBlock   要重新装填的消息块
		pBody		包体，若 pBody 为空值，则 pMsgBlock 保持原来的包体内容不变
		iBodyLen	包体长度，若 pBody 为空值，则 pMsgBlock 保持原来的包体内容不变
	传出参数：
		pMemBlock	重新装填好的消息块，通道号保持原来的值不变
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	包体内容超出消息块的长度啦
			20	消息块出池失败
			40	包头或包体赋值失败
	--------------------------------------------------------------------------*/
	int refill_content(CMemBlock2*& pMemBlock, const char* pBody, int iBodyLen);

	/* --------------------------------------------------------------------------
	函数说明：重新装填消息块，通道号保持原来的值不变
	传入参数：
	    pMemBlock   要重新装填的消息块
		pBody		包体，若 pBody 为空值，则 pMsgBlock 保持原来的包体内容不变
		iBodyLen	包体长度，若 pBody 为空值，则 pMsgBlock 保持原来的包体内容不变
	传出参数：
		pMemBlock	重新装填好的消息块，通道号保持原来的值不变
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	包体内容超出消息块的长度啦
			20	消息块出池失败
			40	包头或包体赋值失败
	--------------------------------------------------------------------------*/
	int refill_content(CMemBlock2*& pMemBlock, int iMainCode, int iSubCode, const char* pBody=NULL, int iBodyLen=0);

	/* --------------------------------------------------------------------------
	函数说明：重新装填消息块
	传入参数：
	    pMemBlock   要重新装填的消息块
		pBody		包体，若 pBody 为空值，则 pMsgBlock 保持原来的包体内容不变
		iBodyLen	包体长度，若 pBody 为空值，则 pMsgBlock 保持原来的包体内容不变
	传出参数：
		pMemBlock	重新装填好的消息块，通道号保持原来的值不变
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	包体内容超出消息块的长度啦
			20	消息块出池失败
			40	包头或包体赋值失败
	--------------------------------------------------------------------------*/
	int refill_content(CMemBlock2*& pMemBlock, int64_t lChannelNo, int iMainCode, int iSubCode, const char* pBody=NULL, int iBodyLen=0);

	/* --------------------------------------------------------------------------
	函数说明：向消息块追加内容
	传入参数：
	    pMemBlock   要重新装填的消息块
		pBody		包体
		iBodyLen	包体长度
	传出参数：
		pMemBlock	重新装填好的消息块
	返回值：
		成功则返回 0，失败返回错误码如下：
			 5	pMemBlock 为空。
			10	包体内容超出消息块的长度啦
			20	pMemBlock 长度太短了
			40	包头或包体赋值失败
	--------------------------------------------------------------------------*/
	int append_content(CMemBlock2*& pMemBlock, const char* pBody, int iBodyLen);

	// 出池一个空的消息块，成功返回0，失败返回错误码
	int retrieve_empty_content(char* pszTextMark, CMemBlock2*& pMemBlock, bool bIsWait=true);

	// 出池一个携带错误内容的消息块，主要用于测试
	int retrieve_with_error_content(char* pszTextMark, CMemBlock2*& pMemBlock, int iMainCode, int iSubCode, const char* pBody=NULL, int iBodyLen=0, bool bIsWait=true);

	/* --------------------------------------------------------------------------
	函数说明：出池一个携带指定内容的消息块
	传入参数：
		iMainCode	主消息码
		iSubCode	辅助消息码
		pBody		包体
		iBodyLen	包体长度
		pszTextMark 文字标识，目的是在查询未释放的内存是知道是程序的哪一句申请的。通常是调用的函数名。
		bIsWait		如果从池中取消息记录未能成功，等待 1000 毫秒
	传出参数：
		pMemBlock		出池的 CMemBlock2 实例，需要手工归池
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	包体内容超出消息块的长度啦
			20	消息块出池失败
			40	包头或包体赋值失败
	--------------------------------------------------------------------------*/
	int retrieve_with_content_no_channel(char* pszTextMark, CMemBlock2*& pMemBlock, int iMainCode, int iSubCode, const char* pBody=NULL, int iBodyLen=0, bool bIsWait=true);

	// 出池一个携带指定内容的消息块
	/* --------------------------------------------------------------------------
	函数说明：出池一个携带指定内容的消息块
	传入参数：
		lChannelNo	通道号
		iMainCode	主消息码
		iSubCode	辅助消息码
		pBody		包体
		iBodyLen	包体长度
		pszTextMark 文字标识，目的是在查询未释放的内存是知道是程序的哪一句申请的。通常是调用的函数名。
		bIsWait		如果从池中取消息记录未能成功，等待 1000 毫秒
	传出参数：
		pMemBlock		出池的 CMemBlock2 实例，需要手工归池
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	包体内容超出消息块的长度啦
			20	消息块出池失败
			40	包头或包体赋值失败
	--------------------------------------------------------------------------*/
	int retrieve_with_content(char* pszTextMark, CMemBlock2*& pMemBlock, int64_t lChannelNo, int iMainCode, int iSubCode, const char* pBody, int iBodyLen, bool bIsWait=true);

	/* --------------------------------------------------------------------------
	函数说明：获得内核消息的真实数据
	传入参数：
		lChannelNo	通道号
		iMainCode	主消息码
		iSubCode	辅助消息码
		pData		包体
		iDataLen	包体的长度		
	传出参数：
		pMemBlock	携带内核的消息块
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	pMemBlock 是空值
			20	pMemBlock 长度太短了
	--------------------------------------------------------------------------*/
	int get_core_msg_data(CMemBlock2* pMemBlock, int* iMainCode, int* iSubCode, char*& pData, int* iDataLen);

	/* --------------------------------------------------------------------------
	函数说明：获得内核消息的真实数据，服务端专用
	传入参数：
		pMemBlock	携带内核的消息块
	传出参数：
		lChannelNo	通道号
		iMainCode	主消息码
		iSubCode	辅助消息码
		pData		包体
		iDataLen	包体的长度
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	pMemBlock 是空值
			20	pMemBlock 长度太短了
	--------------------------------------------------------------------------*/
	int get_core_msg_data(CMemBlock2* pMemBlock, int64_t* lChannelNo, int* iMainCode, int* iSubCode, char*& pData, int* iDataLen);

	/* --------------------------------------------------------------------------
	函数说明：获得内核消息的三个主要数据
	传入参数：
		pMemBlock	携带内核的消息块
	传出参数：
		lChannelNo	通道号
		iMainCode	主消息码
		iSubCode	辅助消息码
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	pMemBlock 是空值
			20	pMemBlock 长度太短了
	--------------------------------------------------------------------------*/
	int get_core_msg_three_param(CMemBlock2* pMemBlock, int64_t& lChannelNo, int& iMainCode, int& iSubCode);

	/* --------------------------------------------------------------------------
	函数说明：获得内核消息的头
	传入参数：
		pMemBlock	携带内核的消息块
	传出参数：
		pHead		内核消息的头
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	pMemBlock 是空值
			20	pMemBlock 长度太短了
	--------------------------------------------------------------------------*/
	int get_core_msg_head(CMemBlock2* pMemBlock, CORE_MSG_HEAD*& pMsgHead);

	/* --------------------------------------------------------------------------
	函数说明：获得内核消息的通道号
	传入参数：
		pMemBlock	携带内核的消息块
	传出参数：
		lChannelNo		通道号
	返回值：
		成功则返回 0，失败返回错误码如下：
			10	pMemBlock 是空值
			20	pMemBlock 长度太短了
	--------------------------------------------------------------------------*/
	int get_core_msg_channel(CMemBlock2* pMemBlock, int64_t& lChannelNo);

	// 获得内核消息头内的参数值
	int get_core_head_param(CMemBlock2* pMemBlock, TYPE_IN_HEAD typeInHead, int& iVal);

	// 归池
	//bool recycle(CMemBlock2* pMsgBlock, char* szFunName=NULL);
	bool recycle(CMemBlock2* pMsgBlock, const char* szFunName);

	// 获得池内当前元素的数量
	int get_current_element_count();

	// 获得出池的数量
	int64_t get_retrieve_count();

	// 获得归池的数量
	int64_t get_recycle_count();

	/* --------------------------------------------------------------------------
	函数说明：收缩池的尺寸，也可以启一个线程来进行收缩会更安全
	传入参数：
		nAtLessShrinkCount	至少要收缩的数目
		fShrinkParam 收缩系数
	返回值：
		实际收缩的尺寸
	--------------------------------------------------------------------------*/
	int	 shrink_pool(int nAtLessShrinkCount = 512, float fShrinkParam = 0.5);	

	/* --------------------------------------------------------------------------
	函数说明：列印未归池（在池外）的元素
	传入参数：
		bDisplay		是否列印
		int iDisplayNum	未归池的最大显示数量
	传出参数：
		vecOutPool	未归池（在池外）的元素数组
	返回值：
		成功则返回 0，失败返回错误码
	--------------------------------------------------------------------------*/
	int display_out_pool(bool bDisplay, std::vector<OUT_POOL_ELEMENT>& vecOutPool, int iDisplayNum=20);

	// 显示所有的池信息
	void display_all_pool();

private:
	// 扩展池的尺寸
	int expand_pool(char* pszTextMark, int nCount, bool bLimitExpandLen = true);		

public:
		//static CMemBlockPool2* _memBlockPool;

private:
	
	std::deque<CMemBlock2*>	m_stack;	// 池

	typedef std::map<CMemBlock2*, int> MAP_ORIGINAL_STUB;
	typedef std::pair<CMemBlock2*, int> PAIR_ORIGINAL_STUB;
	MAP_ORIGINAL_STUB	m_mapOriginalStub;	// 原始的存根，包括了扩展后与收缩后的 CMemBlock2* ，
																					// 无论 CMemBlock2* 归池与出池都包括，用于查询未归池的 CMemBlock2*

	std::recursive_mutex   m_lock;		// 递归互斥，recursive（递归）

	//char	m_szPoolName[128];	// 池的名称
	int		m_iMsgBlockSize;	// 消息元素的长度

	bool			m_bShrinkPool;	// 是否允许收缩

	float			m_fExpandParam;	// 扩展系数，收缩时只收至初始值乘以扩充系数之处

	std::atomic<int>	m_nInitElementCount;	// 池内初始元素的数量
	std::atomic<int>	m_nMaxElementCount;		// 池内元素的上限值

	std::atomic<int64_t > m_lRetrieveCount;	// 出池的数量
	std::atomic<int64_t > m_lRecycleCount ;	// 归池的数量

	std::atomic<int>  m_nAfterExpandOrShrinkCount;	// 池的元素数量，主要是扩展或收缩池后的数量，当消息归池或出池时并不影响它

	int		m_nExpandStep;			// 扩展步长，池内无元素时需要扩展的数量
	int		m_lPeriod;				// 定时收缩的时间间隔，以 ms 为单位

	int		m_iExpandFailTimes;		// 扩展失败次数
	int		m_iExpandFailSleepTime;	// 扩展失败时的睡眠时间，以 ms 为单位

	int  m_iDieSpace;	// ms 值，未归池的时差判定的时长，因为 display_out_pool 时还有一些正常消息在使用中暂未归池，这样的守护不应该被列为未归池（在池外）的元素
									// 只有在时间上大于此值才能被认定为未归池（在池外）的元素
};

#endif